'use strict'

const pcConfig = {
    'iceServers':[{
      'urls':'turn:stun.al.learningrtc.cn:3478',
      'credential':'mypassword',
      'username':'garrylea'
    }]
}

const localVideo = document.querySelector('video#localvideo');
const remoteVideo = document.querySelector('video#remotevideo');


const btnConn = document.querySelector('button#connserver');
const btnLeave = document.querySelector('button#leave');

const optBw = document.querySelector('select#bandwidth');

const shareDesk = document.querySelector('input#shareDesk');

const chat = document.querySelector('textarea#chat');
const send_txt = document.querySelector('textarea#sendtxt');
const btnSend = document.querySelector('button#send');

const fileInput = document.querySelector('input#fileInput');

const btnSendFile = document.querySelector('button#sendFile');
const btnAbort = document.querySelector('button#abortButton');

const sendProgress = document.querySelector('progress#sendProgress');
const receiveProgress = document.querySelector('progress#receiveProgress');

const statusMessage = document.querySelector('span#status');
const downloadAnchor = document.querySelector('a#download');

btnConn.onclick = connSignalServer;
btnLeave.onclick = leave;
optBw.onchange = change_bw;
btnSend.onclick = sendText;


btnSendFile.onclick = sendFile;
btnAbort.onclick = abort;
fileInput.onchange = handleFileInputChange;

let socket = null;
let roomid = '111111';

let localStream = null;
let remoteStream = null;
let state = 'init';

let pc = null;
let dc = null;

let offerDesc = null;

let bitrateGraph;
let bitrateSeries;

let packetGraph;
let packetSeries;

let lastResult;

let fileReader = null;
let fileName = "";
let fileSize = 0;

let lastModifyTime = 0;
let fileType = "data";

let receiveBuffer = [];
let receivedSize = 0;

function conn() {
  socket = io.connect();
  socket.on('joined', (roomid, sessionid) => {
    console.log('receive joined message!', roomid, sessionid);
    state = 'joined'
    createPeerConnection();
    bindTracks();

    btnConn.disabled = true;
    btnLeave.disabled = false;
    console.log('receive joined message state = ', state);
  });
  socket.on('otherjoin', (roomid) => {
      console.log('receive otherjoin message!', roomid);
      if(state === 'joined_unbind') {
        createPeerConnection();
        bindTracks();
      }
      if(!dc){
        dc = pc.createDataChannel('chat', {negotiated: true, id: 0})
        dc.onmessage = receiveMessage;
        dc.onopen = dataChannelStateChange;
        dc.onclose = dataChannelStateChange;
      }
      state = 'joined_conn';
      call();
      console.log('receive other_join message state = ', state,);
  });
  socket.on('full', (roomid, sessionid) => {
    console.log('receive full message', roomid, sessionid);
    socket.disconnect();
    hangup();
    closeLocalMedia();
    state = 'leaved';
    console.log('remote full message state = ', state);
    
    alert('the room is full!');
  });
  socket.on('leaved', (roomid, sessionid) => {
    console.log('receive leaved message', roomid, sessionid);
    state = 'leaved';
    socket.disconnect();
    console.log('receive leaved message, state = ', state);
    btnConn.disabled = false;
    btnLeave.disabled = true;
    optBw.disabled = true;
  });
  socket.on('bye', (roomid, sessionid) => {
    console.log('receive bye message', roomid, sessionid);
    state = 'joined_unbind';
    hangup();
    console.log('receive bye message, state = ', state);
  });
  socket.on('disconnect', (socket) => {
    console.log('receive disconnect message', roomid);
    //异常退出情况
    if(state != 'leaved'){
        hangup();
        closeLocalMedia();
    }
    state = 'leaved';
    
    btnConn.disabled = false;
    btnLeave.disabled = true;
    optBw.disabled = true; 
  });
  socket.on('message', (roomid, data) => {
    console.log('receive message', roomid, data);
    if(data == null || data === undefined) {
      console.error('the message is invalid');
      return;
    }

    if(data.hasOwnProperty('type') && data.type === 'offer') {
      pc.setRemoteDescription(new RTCSessionDescription(data));
      pc.createAnswer()
      .then(getAnswer)
      .catch(handleAnswerError);
      if(!dc){
        dc = pc.createDataChannel('chat',{negotiated: true, id: 0})
        dc.onmessage = receiveMessage;
        dc.onopen = dataChannelStateChange;
        dc.onclose = dataChannelStateChange;
      }
    } else if(data.hasOwnProperty('type') && data.type === 'answer') {
      optBw.disabled = false;
      pc.setRemoteDescription(new RTCSessionDescription(data));
    } else if(data.hasOwnProperty('type') && data.type === 'candidate') {
      let candidate = new RTCIceCandidate({
        sdpMLineIndex: data.label,
        sdpMid: data.id,
        candidate: data.candidate
      });
      pc.addIceCandidate(candidate).then(() => {
        console.log('success to add ice candidate');
      }).catch((e) =>{
        console.error(e);
      });
    } else if (data.hasOwnProperty('type') && data.type === 'fileinfo') {
      fileName = data.name;
      fileType = data.fileType;
      fileSize = data.size;
      lastModifyTime = data.lastModify;
      receiveProgress.max = fileSize;
    } else {
      console.log('the message is invalid!', data);
    }
  })
  socket.emit('join', roomid)
  return true;
}

function getOffer(desc) {
  pc.setLocalDescription(desc);
  offerDesc = desc;
  sendMessage(roomid, offerDesc);
}


function handleOfferError(error) {
  console.error('Failed to create offer:', error);
}

function getAnswer(desc) {
  pc.setLocalDescription(desc)
  sendMessage(roomid, desc);
  optBw.disabled = false;
}

function handleAnswerError(error) {
  console.error('Failed to create answer:', error);
  
}

function getRemoteStream(stream) {
  console.log('get Remote Stream',  stream.streams[0]);
  remoteStream = stream.streams[0];
  remoteVideo.srcObject = remoteStream;
}


function createPeerConnection() {
 console.log('create RTCPeerConnection!');
 if(!pc) {
  pc = new RTCPeerConnection(pcConfig);
  pc.onicecandidate = (e) => {
    if(e.candidate) {
        sendMessage(roomid, {
          type: 'candidate',
          label: e.candidate.sdpMLineIndex,
          id:e.candidate.sdpMid,
          candidate: e.candidate.candidate
        })
    } else {
      console.log('this is the  end candidate');
    }
  }
   pc.ondatachannel = (e) => {
    if(!dc){
      dc = e.channel;
      dc.onmessage = receiveMessage;
      dc.onopen = dataChannelStateChange;
      dc.onclose = dataChannelStateChange;
    }
   }
   pc.ontrack = getRemoteStream;
 } else {
   console.log('the pc have be created')
 }
}

function bindTracks() {
  if(pc === null || pc === undefined){
    console.error('pc is null or undefined');
    return;
  }

  if(localStream === null || localStream === undefined) {
    console.error('local stream is null or undefined');
    return;
  }
  localStream.getTracks().forEach((track) => {
    pc.addTrack(track, localStream);
  })
}

function sendMessage(roomid, data) {
  console.log('send message to other end', roomid, data);
  if(!socket) {
    console.error('socket is null');
    return;
  }
  socket.emit('message', roomid, data);
}


function call() {
  if(state === 'joined_conn'){
    let offerOptions = {
      iceRestart: false,
      offerToReceiveAudio: 1,
      offerToReceiveVideo: 1
    }
    pc.createOffer(offerOptions)
    .then(getOffer)
    .catch(handleOfferError)
  }
}

function hangup() {
  if(!pc) { 
    return;
  }

  offerDesc = null;
  pc.close();
  pc = null;

 
}

function getMediaStream(stream) {
    localStream = stream;
    localVideo.srcObject = localStream;
    conn();
    bitrateSeries = new TimelineDataSeries();
    bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');
    bitrateGraph.updateEndDate(); 

    packetSeries = new TimelineDataSeries();
    packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');
    packetGraph.updateEndDate(); 
}


function handleError(error) {
    console.error('Failed to get Media Stream or get Display Stream', error);
}


function start() {
  let constraints = {
    video: true,
    audio: true
  }

  if(shareDesk.checked) {
    if(!navigator.mediaDevices ||
      !navigator.mediaDevices.getDisplayMedia
    ){
      console.error('the getDisplayMedia is not supported!');
      return;
    }
    navigator.mediaDevices.getDisplayMedia(constraints)
    .then(getMediaStream)
    .catch(handleError)
  }else {
    //发生错误的时候
    if(!navigator.mediaDevices ||
      !navigator.mediaDevices.getUserMedia) {
      console.error('the getUserMedia is not supported!');
      return;
    }

    navigator.mediaDevices.getUserMedia(constraints)
    .then(getMediaStream)
    .catch(handleError);
  }
}

function connSignalServer() {
   start();
}

function leave() {
  socket.emit('leave', roomid);
  closeLocalMedia();
  btnConn.disabled = false;
  btnLeave.disabled = true;
  optBw.disabled = true;

  if(dc){
    dc.close();
    dc = null;
  }
  send_txt.disabled = true;
  send.disabled = true;
 
}

function closeLocalMedia() {
  if(localStream === null || localStream === undefined) {
    return;
  }

  localStream.getTracks().forEach(track => {
    track.stop();
  });
  localStream = null;
}

function change_bw() {
  optBw.disabled = true;
  let bw = optBw.options[optBw.selectedIndex].value;
  let vsender = null;
  let senders = pc.getSenders();
  senders.forEach((sender) => {
    if(sender && sender.track.kind === 'video') {
      vsender = sender;
    }
  });
  let parameters = vsender.getParameters();
  if(!parameters.encodings) {
      return;
  } 

  if(bw === 'unlimited') {
    return;
  }

  parameters.encodings[0].maxBitrate = bw * 1000;
  vsender.setParameters(parameters)
  .then(() => {
    optBw.disabled = false;
    console.log('success to set parameters!');
  })
  .catch((error) => {
    console.error(error);
  });
}

function receiveMessage(msg) {
 const message = msg.data;
 
 if(message) {
  const obj = JSON.parse(message);
    if(obj.type === 'text'){
      chat.value += '->' + obj.data + '\r\n';
    }

    if(obj.type === 'file') {
      const arrayBuffer = base64ToArrayBuffer(obj.data);
      receiveBuffer.push(arrayBuffer);
      receivedSize += arrayBuffer.byteLength;
      console.log(receivedSize);
      receiveProgress.value = receivedSize;
      if(receivedSize === fileSize) {
        const received = new Blob(receiveBuffer);
        receiveBuffer = [];
        downloadAnchor.href = URL.createObjectURL(received);
        downloadAnchor.download = fileName;
        downloadAnchor.textContent = `click to download ${fileName} (${fileSize} bytes)`
      }
    }
  // console.log(message);
 } else {
  console.error('received message is null');
 }
}

function dataChannelStateChange() {
  if(!dc){
     console.log('send channel dc is null');
    return;
  }
  let readyState = dc.readyState;
  console.log('send channel state is ' + readyState);
  if(readyState === 'open') {
    send_txt.disabled = false;
    send.disabled = false;
    fileInput.disabled = false;
  } else {
    send_txt.disabled = true;
    send.disabled = true;
    fileInput.disabled = true;
  }

}


function sendText() {
    let data = send_txt.value;
    const obj = {
      data: data,
      type: "text",
    };
    if(data != null){
      dc.send(JSON.stringify(obj));
    }
    send_txt.value = '';
    chat.value += '<-' + data + '\r\n';
}


window.setInterval(()=> {
  if(!pc) {
    return;
  }

  const sender = pc.getSenders()[0];
  if(!sender) {
    return;
  }

  sender.getStats().then((res) => {
    res.forEach((report) => {
      let bytes;
      let packets;
      if(report.type === 'outbound-rtp') {
          if(report.isRemote) {
            return;
          }
          const now =  report.timestamp;
          bytes =  report.bytesSent;
          packets = report.packetsSent;
          if(lastResult && lastResult.has(report.id)) {
            const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /
            (now - lastResult.get(report.id).timestamp);

            bitrateSeries.addPoint(now, bitrate);
            bitrateGraph.setDataSeries([bitrateSeries]);
            bitrateGraph.updateEndDate();

            packetSeries.addPoint(now, packets - lastResult.get(report.id).packetsSent);
            packetGraph.setDataSeries([packetSeries]);
            packetGraph.updateEndDate();
          }
      }
    });
    lastResult = res;
  });
}, 1000);


function arrayBufferToBase64(buffer) {
  let binary = '';
  const bytes = new Uint8Array(buffer);
  const len = bytes.byteLength;
  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return window.btoa(binary);
}

function base64ToArrayBuffer(base64) {
  const binaryString = window.atob(base64);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes.buffer;
}


function sendData() {
  let offset = 0;
  const chunkSize = 1024 * 16
  const file = fileInput.files[0];
  console.log(`file is ${[file.name, file.size, file.type, file.lastModified].join(' ')}`);
  statusMessage.textContent = ''
  downloadAnchor.textContent = ''
  if(file.size === 0) {
    statusMessage.textContent = 'File is empty, please select a non-empty file';
    return;
  }

  sendProgress.max = file.size;

  fileReader = new FileReader();
  fileReader.onerror = (error) => {
    console.error('Error reading file', error);
  }
  fileReader.onabort = (event) => {
    console.log('file reading aborted', event);
  }

  fileReader.onload = (e) => {
    console.log('fileReader onload', e);
    const base64Data = arrayBufferToBase64(e.target.result);
    const obj = {
      data: base64Data,
      type: "file",
    };
    dc.send(JSON.stringify(obj));
    offset += e.target.result.byteLength;
    sendProgress.value = offset;
    if(offset < file.size) {
      readSlice(offset);
    }
  }
  const readSlice = (o) => {
    console.log('readSlice', o);
    const slice = file.slice(offset, o+chunkSize);
    fileReader.readAsArrayBuffer(slice);
  }
  readSlice(0);
}



function sendFile() {
  sendData();
  btnSendFile.disabled = true;
}

function abort() {
 if(fileReader && fileReader.readyState === 1) {
  console.log('abort read');
  fileReader.abort();
 }
}

function handleFileInputChange() {
  const file = fileInput.files[0];
  if(!file){
    console.log('No file choose');
    return;
  }

  fileName = file.name;
  fileSize = file.size;
  fileType = file.type;
  lastModifyTime = file.lastModified;

  sendMessage(roomid, {
    type: 'fileinfo',
    name: file.name,
    size: file.size,
    filetype: file.type,
    lastModified: file.lastModified
  });
  btnSendFile.disabled = false;
  sendProgress.value = 0;
  receiveProgress.value = 0;
  
  receiveBuffer = [];
  receivedSize = 0;
}